{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Week 2 Day 3 - LAB contribution\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Added functionality\n",
    "\n",
    "#### 1. Lead Qualification System with Structured Output\n",
    "Add a lead scoring agent that evaluates prospects before sending emails:\n",
    "\n",
    "- Create a ```LeadScore``` Pydantic model with fields  ```score``` (0-100), ```decision``` (send/skip), ```reasoning```, and ```suggested_tone```(professional/witty/concise)\n",
    "- Build a lead qualification agent that takes company info (size, industry, funding stage) and outputs this structured format\n",
    "- Instruct the sales manager to verify the lead score is above a threshold before proceeding\n",
    "- The lead scorer chooses which sales_agent tool to use based on the prospect profile, instead of generating all 3 e-mails.\n",
    "\n",
    "#### 2. Compliance & Brand Safety Layer\n",
    "Extend the guardrail system for enterprise use:\n",
    "\n",
    "- Create a ```ComplianceCheck``` structured output ```(violations: List[str], risk_level: str, approved: bool)```\n",
    "- Build a compliance agent that checks for unsubstantiated claims, regulatory issues, or brand guideline violations\n",
    "- Add both input guardrails (blocking certain industry targets) and output guardrails (verifying email meets compliance standards before sending)\n",
    "- Create a \"revision loop\" where non-compliant emails get sent back to the sales agents with specific feedback for regeneration\n",
    "\n",
    "#### 3. Added some context\n",
    "1. Added context created during the previous lab exercise to help agents write better e-mails and make better decisions. This was necessary to avoid constant loops, as the compliance checker tends to be on the strict side without context (everything was unsubstantiated)\n",
    "2. Tweaked the prompts\n",
    "\n",
    "##### Caveats:\n",
    "- Limited testing, there will be bugs (you have been warned!)\n",
    "\n",
    "#### Updated flow: \n",
    "\n",
    "Lead Scoring → Single Email Generation → Compliance Check → (Guardrail) → Send"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from openai import AsyncOpenAI\n",
    "from agents import Agent, Runner, trace, function_tool, OpenAIChatCompletionsModel, input_guardrail, GuardrailFunctionOutput, output_guardrail\n",
    "from typing import Dict, List, Literal\n",
    "import sendgrid\n",
    "import os\n",
    "from sendgrid.helpers.mail import Mail, Email, To, Content\n",
    "from pydantic import BaseModel, ConfigDict\n",
    "from datetime import datetime, date\n",
    "import json\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "openai_api_key = os.getenv('OPENAI_API_KEY')\n",
    "google_api_key = os.getenv('GEMINI_API_KEY')\n",
    "deepseek_api_key = os.getenv('DEEPSEEK_API_KEY')\n",
    "groq_api_key = os.getenv('GROQ_API_KEY')\n",
    "\n",
    "if openai_api_key:\n",
    "    print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
    "else:\n",
    "    print(\"OpenAI API Key not set\")\n",
    "\n",
    "if google_api_key:\n",
    "    print(f\"Google API Key exists and begins {google_api_key[:2]}\")\n",
    "else:\n",
    "    print(\"Google API Key not set (and this is optional)\")\n",
    "\n",
    "if deepseek_api_key:\n",
    "    print(f\"DeepSeek API Key exists and begins {deepseek_api_key[:3]}\")\n",
    "else:\n",
    "    print(\"DeepSeek API Key not set (and this is optional)\")\n",
    "\n",
    "if groq_api_key:\n",
    "    print(f\"Groq API Key exists and begins {groq_api_key[:4]}\")\n",
    "else:\n",
    "    print(\"Groq API Key not set (and this is optional)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create sales agent instructions dict\n",
    "\n",
    "today = str(date.today())\n",
    "\n",
    "with open('ComplAI_brochure.md', 'r') as file:\n",
    "    brochure_text = file.read()\n",
    "\n",
    "instructions_with_context_1 = f\"\"\"## Persona\n",
    "You are a sales agent working for ComplAI, a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \n",
    "\n",
    "## Context\n",
    "Here is some information about the product which you can use: {brochure_text}\n",
    "\n",
    "## Tone \n",
    "You write professional, serious cold emails. \\nToday's date is {today}\"\"\"\n",
    "\n",
    "instructions_with_context_2 = f\"\"\"## Persona\n",
    "You are a sales agent working for ComplAI, a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \n",
    "\n",
    "## Context\n",
    "Here is some information about the product which you can use: {brochure_text}\n",
    "\n",
    "## Tone \n",
    "You write witty, serious cold emails. \\nToday's date is {today}\"\"\"\n",
    "\n",
    "instructions_with_context_3 = f\"\"\"## Persona\n",
    "You are a sales agent working for ComplAI, a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \n",
    "\n",
    "## Context\n",
    "Here is some information about the product which you can use: {brochure_text}\n",
    "\n",
    "## Tone \n",
    "You write concise, serious cold emails. \\nToday's date is {today}\"\"\"\n",
    "\n",
    "brand_guidelines = f\"\"\"\n",
    "\\n## BRAND GUIDELINES:\n",
    "- Never make unsubstantiated ROI claims\n",
    "- Don't use superlatives like \"best\" or \"revolutionary\" \n",
    "- Focus on time-saving and audit preparation benefits\n",
    "- Keep technical jargon minimal\n",
    "- Always mention our SOC2 certification\n",
    "\n",
    "## Personalization\n",
    "Use these pain points to personalize: {{pain_points}}\n",
    "\"\"\"\n",
    "\n",
    "instructions_with_context_1 += brand_guidelines\n",
    "instructions_with_context_2 += brand_guidelines\n",
    "instructions_with_context_3 += brand_guidelines\n",
    "\n",
    "AGENT_INSTRUCTIONS = {\n",
    "    \"professional\": instructions_with_context_1,\n",
    "    \"witty\": instructions_with_context_2,\n",
    "    \"concise\": instructions_with_context_3\n",
    "}\n",
    "\n",
    "# Create generic sales e-mail generation tool\n",
    "# Instead of generating 3 different e-mails we will generate the one that the lead_scoring tool will recommend\n",
    "\n",
    "@function_tool\n",
    "async def generate_sales_email(\n",
    "    tone: str,\n",
    "    pain_points: str,\n",
    "    target_info: str = \"\") -> str: \n",
    "\n",
    "    \"\"\" Generate a personalized cold sales e-mail with the requested tone. \n",
    "    The e-mail covers the pain_points of the target described in target_info.\n",
    "    \"\"\"\n",
    "    \n",
    "    if(tone not in AGENT_INSTRUCTIONS):\n",
    "        tone = 'concise' # Default to concise if key is not recognized\n",
    "    \n",
    "    instructions = AGENT_INSTRUCTIONS[tone].format(pain_points = pain_points)\n",
    "    agent = Agent(\n",
    "        name=f\"{tone} Sales Agent\",\n",
    "        instructions=instructions,\n",
    "        model=\"gpt-4o-mini\"\n",
    "    )\n",
    "    \n",
    "    result = await Runner.run(agent, f\"Write a cold email addressing: {target_info}\")\n",
    "    return result.final_output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### It's easy to use any models with OpenAI compatible endpoints"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
    "#DEEPSEEK_BASE_URL = \"https://api.deepseek.com/v1\"\n",
    "#GROQ_BASE_URL = \"https://api.groq.com/openai/v1\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "#deepseek_client = AsyncOpenAI(base_url=DEEPSEEK_BASE_URL, api_key=deepseek_api_key)\n",
    "#gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)\n",
    "#groq_client = AsyncOpenAI(base_url=GROQ_BASE_URL, api_key=groq_api_key)\n",
    "\n",
    "#deepseek_model = OpenAIChatCompletionsModel(model=\"deepseek-chat\", openai_client=deepseek_client)\n",
    "#gemini_model = OpenAIChatCompletionsModel(model=\"gemini-2.0-flash\", openai_client=gemini_client)\n",
    "#llama3_3_model = OpenAIChatCompletionsModel(model=\"llama-3.3-70b-versatile\", openai_client=groq_client)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Lead Scoring Agent\n",
    "\n",
    "class LeadScore(BaseModel):\n",
    "    score: int  # 0-100\n",
    "    decision: str  # \"send\" or \"skip\"\n",
    "    reasoning: str\n",
    "    recommended_agent: str  # \"sales_agent1\", \"sales_agent2\", or \"sales_agent3\"\n",
    "    key_pain_points: List[str]\n",
    "\n",
    "lead_scorer_instructions = f\"\"\"You are scoring leads for a cold e-mail outreach campaign for ComplAI, \n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI.\n",
    "Analyze prospect info and determine:\n",
    "    - Lead quality score (0-100)\n",
    "    - Which sales agent tone fits best (professional/witty/concise)\n",
    "    - Key pain points to address\n",
    "    Only recommend sending if score >= 60\n",
    "    \n",
    "    Information about the tool that may help your decision: {brochure_text}\"\"\"\n",
    "\n",
    "lead_scorer_agent = Agent(\n",
    "    name=\"Lead Qualifier\",\n",
    "    instructions=lead_scorer_instructions,\n",
    "    output_type=LeadScore,\n",
    "    model=\"gpt-4o-mini\"\n",
    ")\n",
    "\n",
    "lead_scorer = lead_scorer_agent.as_tool(tool_name = \"lead_scorer\", tool_description= \"Perform Lead Scoring\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Compliance Checker \n",
    "\n",
    "class ComplianceCheck(BaseModel):\n",
    "    model_config = ConfigDict(strict=True)\n",
    "    \n",
    "    approved: bool\n",
    "    violations: List[str] = []  # empty if approved\n",
    "    risk_level: Literal['low', 'medium', 'high'] # \"low\", \"medium\", \"high\"\n",
    "\n",
    "\n",
    "compliance_checker_instructions = f\"\"\"Review email against brand guidelines:\n",
    "    - Flag unsubstantiated claims (ROI %, time savings without data)\n",
    "    - Check for superlatives/hyperbole (e.g. \"guaranteed compliance\", \"absolute best\", \"no.1 in the market\")\n",
    "    - Marketing language is ok as long as \n",
    "    - Verify pain points are addressed professionally\n",
    "    - Ensure ComplAI SOC2 certification is mentioned. This doesn't need further substantiation.\n",
    "    - Assess email risk as 'low', 'medium' or 'high'\n",
    "    Approve only if risk_level is 'low'    \n",
    "    Respond with:\n",
    "    - approved: (True/False)\n",
    "    - violations: comma separated list of brand guidelines violations\n",
    "    - risk_level: (low/medium/high)\n",
    "\n",
    "    Product information:\n",
    "    {brochure_text}\n",
    "\"\"\" \n",
    "\n",
    "compliance_agent = Agent(\n",
    "    name=\"compliance_checker\",\n",
    "    instructions=compliance_checker_instructions,\n",
    "    output_type=ComplianceCheck,\n",
    "    model=\"gpt-5-mini\"\n",
    ")\n",
    "\n",
    "compliance_checker = compliance_agent.as_tool(tool_name = \"compliance_checker\", tool_description = \"Check for brand compliance\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#sales_agent1 = Agent(name=\"DeepSeek Sales Agent\", instructions=instructions1, model=deepseek_model)\n",
    "#professional_sales_agent =  Agent(name=\"Professional Sales Agent\", instructions=instructions_with_context_1, model=\"gpt-4o-mini\")\n",
    "#witty_sales_agent =  Agent(name=\"Witty Sales Agent\", instructions=instructions_with_context_2, model=\"gpt-4o-mini\")\n",
    "#concise_sales_agent =  Agent(name=\"Concise Sales Agent\", instructions=instructions_with_context_3, model=\"gpt-4o-mini\")\n",
    "#sales_agent3  = Agent(name=\"Llama3.3 Sales Agent\",instructions=instructions3,model=llama3_3_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#description = \"Write a cold sales email\"\n",
    "\n",
    "\n",
    "#tool1 = professional_sales_agent.as_tool(tool_name=\"professional_sales_agent\", tool_description=description)\n",
    "#tool2 = witty_sales_agent.as_tool(tool_name=\"witty_sales_agent\", tool_description=description)\n",
    "#tool3 = concise_sales_agent.as_tool(tool_name=\"concise_sales_agent\", tool_description=description)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@function_tool\n",
    "def send_html_email(subject: str, html_body: str) -> Dict[str, str]:\n",
    "    \"\"\" Send out an email with the given subject and HTML body to all sales prospects \"\"\"\n",
    "    sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))\n",
    "    from_email = Email(\"lchanio@echyperion.com\")  # Change to your verified sender\n",
    "    to_email = To(\"proklos+sg@gmail.com\")  # Change to your recipient\n",
    "    content = Content(\"text/html\", html_body)\n",
    "    mail = Mail(from_email, to_email, subject, content).get()\n",
    "    sg.client.mail.send.post(request_body=mail)\n",
    "    return {\"status\": \"success\"}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@function_tool\n",
    "def add_email_signature(email_body: str, name: str = 'Sales Representative', phone :str = '', email:str = '') -> str:\n",
    "    \"\"\" Add a professional email signature to the email body before HTML formatting \"\"\"\n",
    "\n",
    "    \n",
    "    signature = f\"\\n\\nBest regards,\\n{name}\"\n",
    "    \n",
    "    if phone:\n",
    "        signature += f\"\\nPhone: {phone}\"\n",
    "    if email:\n",
    "        signature += f\"\\nEmail: {email}\"\n",
    "    \n",
    "    signature += \"\\n\\nComplAI - AI-Powered SOC2 Compliance Solutions\"\n",
    "    signature += \"\\nMaking compliance simple, secure, and smart.\"\n",
    "    \n",
    "    return email_body + signature"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "signature_instructions = \"You add professional email signatures to email bodies. \\\n",
    "You are given an email body and signature information (name, phone, email) and you need to add a well-formatted signature.\"\n",
    "\n",
    "signature_writer = Agent(name=\"Email signature writer\", instructions=signature_instructions, model=\"gpt-4o-mini\")\n",
    "signature_tool = signature_writer.as_tool(tool_name=\"signature_writer\", tool_description=\"Add a professional email signature to the email body\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "subject_instructions = \"You can write a subject for a cold sales email. \\\n",
    "You are given a message and you need to write a subject for an email that is likely to get a response.\"\n",
    "\n",
    "html_instructions = \"You can convert a text email body to an HTML email body. \\\n",
    "You are given a text email body which might have some markdown \\\n",
    "and you need to convert it to an HTML email body with simple, clear, compelling layout and design.\"\n",
    "\n",
    "subject_writer = Agent(name=\"Email subject writer\", instructions=subject_instructions, model=\"gpt-4o-mini\")\n",
    "subject_tool = subject_writer.as_tool(tool_name=\"subject_writer\", tool_description=\"Write a subject for a cold sales email\")\n",
    "\n",
    "html_converter = Agent(name=\"HTML email body converter\", instructions=html_instructions, model=\"gpt-4o-mini\")\n",
    "html_tool = html_converter.as_tool(tool_name=\"html_converter\",tool_description=\"Convert a text email body to an HTML email body\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "email_tools = [subject_tool, add_email_signature, html_tool, send_html_email]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions =\"You are an email formatter and sender. You receive the body of an email to be sent. \\\n",
    "Follow these steps in order: \\\n",
    "1. Use the subject_writer tool to write a subject for the email \\\n",
    "2. Use the add_email_signature tool to add a professional signature (use default signature info: name='Alice Johnson', phone='+1-555-0123', email='alice@complai.com') \\\n",
    "3. Use the html_converter tool to convert the updated body with signature to HTML \\\n",
    "4. Finally, use the send_html_email tool to send the final email with the subject and HTML body.\"\n",
    "\n",
    "\n",
    "emailer_agent = Agent(\n",
    "    name=\"Email Manager\",\n",
    "    instructions=instructions,\n",
    "    tools=email_tools,\n",
    "    model=\"gpt-5-mini\",\n",
    "    handoff_description=\"Convert an email to HTML and send it\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [lead_scorer, generate_sales_email, compliance_checker]\n",
    "handoffs = [emailer_agent]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add a guardrail for always escalating high-risk e-mails. \n",
    "\n",
    "@output_guardrail  \n",
    "async def prevent_high_risk_email_sending(ctx, agent, result):\n",
    "    # Look for compliance check results in the conversation\n",
    "    # Check if there's a high risk email that's being handed off\n",
    "\n",
    "    # Type check - only proceed if there's a handoff\n",
    "    if not hasattr(result, 'handoff') or not result.handoff:\n",
    "        return GuardrailFunctionOutput(tripwire_triggered=False, output_info={})\n",
    "    \n",
    "    if result.handoff.name == \"Email Manager\":\n",
    "        # Look for stored compliance results\n",
    "        compliance_results = []\n",
    "        for message in ctx.messages:\n",
    "            if \"COMPLIANCE_RESULT:\" in str(message.content):\n",
    "                compliance_json = str(message.content).split(\"COMPLIANCE_RESULT:\")[1].split('\\n')[0].strip()\n",
    "                compliance_result = json.loads(compliance_json.lower())\n",
    "                compliance_results.append(compliance_result)\n",
    "        \n",
    "        #use the most recent compliance result\n",
    "        if (compliance_result[-1]['risk_level']=='high'):\n",
    "            return GuardrailFunctionOutput(\n",
    "                tripwire_triggered=True,\n",
    "                output_info={\"reason\": \"High risk email requires human review\"}\n",
    "            )\n",
    "    \n",
    "    return GuardrailFunctionOutput(tripwire_triggered=False, output_info={})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sales_manager_instructions = \"\"\"\n",
    "You are a Sales Manager at ComplAI. Your goal is to find the single best cold sales email using the sales_agent tools.\n",
    " \n",
    "Follow these steps carefully:\n",
    "1. Get lead score using the lead_scorer tool passing the information about the lead.\n",
    "\n",
    "2. If decision is \"skip\", stop and explain why\n",
    "\n",
    "3. If decision is \"send\", use the generate_sales_email tool with:\n",
    "- tone: the recommended_agent from lead scoring\n",
    "- pain_points: the key_pain_points as a comma-separated string\n",
    "- target_info: any relevant prospect details\n",
    "\n",
    "4. Pass the single draft to compliance_checker\n",
    "\n",
    "5. If approved, hand off to Email Manager\n",
    "\n",
    "6. If not approved and risk is medium, regenerate the sales email once with violation feedback.\n",
    "\n",
    "7. If high risk or second failure, stop and escalate to human review\n",
    " \n",
    "Crucial Rules:\n",
    "- You must use the generate_sales_email tool to generate the drafts — do not write them yourself.\n",
    "- You must hand off exactly ONE email to the Email Manager — never more than one.\n",
    "- Never send the e-mail back for regeneration more than once. If there's a second compliance_checker failure, always stop and escalate to human review.\n",
    "- After compliance check, store the result in your context in JSON format as 'COMPLIANCE_RESULT: {result}' before making handoff decision\n",
    "\"\"\"\n",
    "\n",
    "sales_manager = Agent(\n",
    "    name=\"Sales Manager\",\n",
    "    instructions=sales_manager_instructions,\n",
    "    tools=tools,\n",
    "    output_guardrails = [prevent_high_risk_email_sending],\n",
    "    handoffs=handoffs,\n",
    "    model=\"gpt-5-mini\")\n",
    "\n",
    "message = \"Send out a cold sales email addressed to John Doe, Chief Compliance Officer of SmartBank, a growing digital neo-bank in Belgium catering to young entrepreneurs in Europe, from Alice Johnson, Sales Representative at ComplAI.\"\n",
    "message2 = \"Send out a cold sales email addressed to Helen Writer, award-winning children's books author, from Alice Johnson, Sales Representative at ComplAI.\"\n",
    "\n",
    "with trace(\"Automated SDR with Lead Scoring and Compliance\"):\n",
    "    result = await Runner.run(sales_manager, message2, max_turns=15) # The workflow may require more than the default 10 steps if there are multiple retries."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Check out the trace:\n",
    "\n",
    "https://platform.openai.com/traces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left; width:100%\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/exercise.png\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#ff7800;\">Exercise</h2>\n",
    "            <span style=\"color:#ff7800;\">• Try different models<br/>• Add more input and output guardrails<br/>• Use structured outputs for the email generation\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
